import { ref, shallowRef, watchEffect, unref } from "vue";
import type { Ref } from "vue";

interface useAsyncStateOptions<T> {
  immediate?: boolean;
  shallowref?: boolean;
  params?: any[];
  onError?: (error: unknown) => void;
  onSuccess?: (data: T) => void;
  onBefore?: () => void;
}

interface useAsyncStateReturn<T> {
  data: Ref<T | void>;
  loading: Ref<boolean>;
  error: Ref<unknown>;
  execute: (...args: any[]) => Promise<T | void>;
}

export const useAsyncState = <T = unknown>(
  fn: (...args: any[]) => Promise<T>,
  options: useAsyncStateOptions<T> = {}
): useAsyncStateReturn<T> => {
  const {
    immediate = true,
    shallowref = false,
    params = [],
    onError,
    onSuccess,
    onBefore,
  } = options;

  const data = shallowref ? shallowRef<T>() : ref<T>();
  const loading = ref(false);
  const error = ref(null);

  const execute = async (...args: any[]) => {
    try {
      loading.value = true;
      error.value = null;
      onBefore?.();
      const response = await fn(...args);
      data.value = response;
      onSuccess?.(response);
      return response;
    } catch (err: any) {
      error.value = err;
      onError?.(err);
      return void 0;
    } finally {
      loading.value = false;
    }
  };

  if (immediate) {
    watchEffect(() => {
      if (params) {
        execute(...unref(params));
      }
    });
  }

  return {
    data,
    loading,
    error,
    execute,
  };
};
